﻿using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Microsoft.Win32
{
	internal static partial class NativeMethods
	{
		public static class BufferedPaint
		{
			public delegate void PaintAction<TState, TParam>(Graphics g, Rectangle rc, TState state, TParam optParam);

			private class BufferedPaintHandle : SafeHandle
			{
				public BufferedPaintHandle(IntPtr hwnd, IntPtr hdcTarget, ref Rectangle rcTarget, 
					ref BufferedPaintAnimationParams pAnimationParams, out IntPtr phdcFrom, out IntPtr phdcTo) : base(IntPtr.Zero, true)
				{
					RECT rc = rcTarget;
					base.SetHandle(NativeMethods.BeginBufferedAnimation(hwnd, hdcTarget, ref rc, NativeMethods.BufferedPaintBufferFormat.CompatibleBitmap, IntPtr.Zero, ref pAnimationParams, out phdcFrom, out phdcTo));
				}

				public override bool IsInvalid => base.handle == IntPtr.Zero;

				protected override bool ReleaseHandle() => NativeMethods.EndBufferedAnimation(base.handle, true).ToInt32() == 0;
			}

			public static void Paint<TState, TParam>(Graphics g, IWin32Window w, Rectangle rc, PaintAction<TState, TParam> f, TState currentState, TState newState, uint duration, TParam optParam)
			{
				try
				{
					if (System.Environment.OSVersion.Version.Major >= 6 && NativeMethods.BufferedPaintInit() == IntPtr.Zero)
					{
						using (var hdc = new SafeGDIHandle(g))
						{
							if (!hdc.IsInvalid)
							{
								// see if this paint was generated by a soft-fade animation
								if (!NativeMethods.BufferedPaintRenderAnimation(w.Handle, hdc))
								{
									NativeMethods.BufferedPaintAnimationParams animParams = new NativeMethods.BufferedPaintAnimationParams { Duration = duration };

									IntPtr hdcFrom, hdcTo;
									using (var h = new BufferedPaintHandle(w.Handle, hdc, ref rc, ref animParams, out hdcFrom, out hdcTo))
									{
										if (!h.IsInvalid)
										{
											if (hdcFrom != IntPtr.Zero)
											{
												using (Graphics gfxFrom = Graphics.FromHdc(hdcFrom))
													f(gfxFrom, rc, currentState, optParam);
											}
											if (hdcTo != IntPtr.Zero)
											{
												using (Graphics gfxTo = Graphics.FromHdc(hdcTo))
													f(gfxTo, rc, newState, optParam);
											}
										}
										else
										{
											f(g, rc, newState, optParam);
										}
									}
								}
							}
						}
						NativeMethods.BufferedPaintUnInit();
					}
					else
						f(g, rc, newState, optParam);
				}
				catch { }
			}
		}

		[Flags]
		public enum BufferedPaintAnimationStyle
		{
			None = 0,
			Linear = 1,
			Cubic = 2,
			Sine = 3
		}

		public enum BufferedPaintBufferFormat
		{
			CompatibleBitmap,
			Dib,
			TopDownDib,
			TopDownMonoDib
		}

		[DllImport(UXTHEME)]
		public static extern IntPtr BeginBufferedAnimation(IntPtr hwnd, IntPtr hdcTarget,
			ref RECT rcTarget, BufferedPaintBufferFormat dwFormat, IntPtr pPaintParams,
			ref BufferedPaintAnimationParams pAnimationParams, out IntPtr phdcFrom, out IntPtr phdcTo);

		[DllImport(UXTHEME)]
		public static extern IntPtr BufferedPaintInit();

		[DllImport(UXTHEME)]
		[return: MarshalAs(UnmanagedType.Bool)]
		public static extern bool BufferedPaintRenderAnimation(IntPtr hwnd, IntPtr hdcTarget);

		[DllImport(UXTHEME)]
		public static extern IntPtr BufferedPaintStopAllAnimations(IntPtr hwnd);

		[DllImport(UXTHEME)]
		public static extern IntPtr BufferedPaintUnInit();

		[DllImport(UXTHEME)]
		public static extern IntPtr EndBufferedAnimation(IntPtr hbpAnimation, bool fUpdateTarget);

		[StructLayout(LayoutKind.Sequential)]
		public struct BufferedPaintAnimationParams
		{
			private Int32 cbSize, dwFlags;
			private BufferedPaintAnimationStyle style;
			private UInt32 dwDuration;

			public BufferedPaintAnimationParams(BufferedPaintAnimationStyle animStyle = BufferedPaintAnimationStyle.Linear, UInt32 dur = 0)
			{
				cbSize = Marshal.SizeOf(typeof(BufferedPaintAnimationParams));
				dwFlags = 0;
				dwDuration = dur;
				style = animStyle;
			}

			public BufferedPaintAnimationStyle AnimationStyle
			{
				get { return style; }
				set { style = value; }
			}

			public UInt32 Duration
			{
				get { return dwDuration; }
				set { dwDuration = value; }
			}
		}
	}
}
